#ifndef PACKETPROCESSOR_H
    #include "PacketProcessor.h"
#endif
#ifndef MAINWINDOW_H
    #include "mainwindow.h"
#endif

extern mainwindow* mainw;
extern DWORD GetTickCount();

// These opcodes may need to be updated if Joymax updates the client
enum opcodes
{

};

void Log(const char* format, ...)
{
    char output[256] = {0};

    va_list args;
    va_start(args, format);
    vsprintf(output, format, args );
    va_end(args);

    // Add the item to the list
    mainw->ui.Log->addItem(output);

    // Scroll to the bottom
    QListWidgetItem* item = mainw->ui.Log->item(mainw->ui.Log->count() - 1);
    if(item) mainw->ui.Log->scrollToItem(item, QAbstractItemView::EnsureVisible);
}

PacketProcessorz::PacketProcessorz(QTcpSocket* Socket)
{
    sSocket = Socket;

    // Clear buffers
    DoPingPacket = false;
    DoLogin = false;
    PingPacketTime = 0;
    LoginPacketTime = 0;
    count_600D = 0;
    serverid = 0;
    memset(server, 0, 32);
    memset(username, 0, 32);
    memset(password, 0, 32);
}

PacketProcessorz::~PacketProcessorz()
{

}

void PacketProcessorz::Login(const char* user, const char* pass, const char* srv)
{
    for(unsigned int x = 0; x < serverList.size(); ++x)
    {
        if(strcmp(serverList[x].name, srv) == 0)
        {
            serverid = serverList[x].id;
            break;
        }
    }

    // Copy the username and password over
    sprintf(username, user);
    sprintf(password, pass);

    PacketBuilder builder;      //Use a new packet builder because of multiple threads
    builder.SetOpcode(0x6102);
    builder.AppendByte(18);
    builder.AppendWord(strlen(user));
    builder.AppendString(user);
    builder.AppendWord(strlen(pass));
    builder.AppendString(pass);
    builder.AppendByte(0xFF);
    builder.AppendWord(serverid);
    InjectPacket(builder.GetPacket(), true);
}

void PacketProcessorz::OnPacket(tPacket* packet, bool encrypted)
{
    // Set the packet
    reader.SetPacket(packet);

    // Display the opcode
    Log("Packet: 0x%.4X", packet->opcode);

    switch(packet->opcode)
    {
        case 0x5000: // HandShake packets
        {
            unsigned long outsize = 0;
            unsigned char outdata[256] = {0};
            HandShakeApi.PacketReceive(packet, outdata, &outsize);

            sSocket->write((char*)outdata, outsize);
        }break;
        case 0x2001:
        {
            memset(server, 0, 32);

            WORD len = reader.ReadWord();
            reader.ReadString(len, server);

            if(strcmp(server, "GatewayServer") == 0)
            {
                builder.SetOpcode(0x6100);
                builder.AppendByte(18); //locale
                builder.AppendWord(9);
                builder.AppendString("SR_Client");
                builder.AppendDword(252); //version
                InjectPacket(builder.GetPacket(), true);
            }
        }break;
        case 0x600D:
        {
            count_600D++;

            if(count_600D == 6)
            {
                if(packet->size == 2)
                {
                    builder.SetOpcode(0x6101);
                    InjectPacket(builder.GetPacket(), true);
                }
                else
                {
                    sSocket->disconnectFromHost();
                }
            }
            else if(count_600D == 4)
            {
                if(strcmp(server, "AgentServer") == 0)
                {
                    builder.SetOpcode(0x6103);
                    builder.AppendDword(LoginID);           //Login ID
                    builder.AppendWord(strlen(username));   //Username length
                    builder.AppendString(username);         //Username
                    builder.AppendWord(strlen(password));   //Password length
                    builder.AppendString(password);         //Password
                    builder.AppendByte(18);                 //Locale
                    builder.AppendByte(0);
                    builder.AppendByte(2);
                    builder.AppendDword(GetTickCount());    //MAC
                    InjectPacket(builder.GetPacket(), true);
                }
            }
        }break;
        case 0xA101:
        {
            SilkroadSrv nServer;
            mainw->ui.ServerList->clear();
            serverList.clear();

            reader.index += 2;
            WORD len = reader.ReadWord();
            reader.index += len;
            reader.index++;

            char name[64] = {0};

            BYTE b = reader.ReadByte();
            while(b)
            {
                memset(name, 0, 64);

                WORD id = reader.ReadWord();

                len = reader.ReadWord();
                reader.ReadString(len, name);

                if((int)strstr(name, "TR") == 0)
                {
                    for(size_t x = 0; x < strlen(name); ++x)
                    {
                        if(name[x] == '(')
                        {
                            name[x - 1] = 0;
                            break;
                        }
                    }
                }

                nServer.id = id;
                sprintf(nServer.name, "%s", name);
                serverList.push_back(nServer);
                mainw->ui.ServerList->addItem(name);

                reader.index += 2;  // Current
                reader.index += 2;  // Max
                reader.index++;     // State

                b = reader.ReadByte();
            }

            Log("Connected successfully");
            mainw->ui.Login->setEnabled(true);
            DoPingPacket = true;
        }break;
        case 0xA102:
        {
            BYTE success = reader.ReadByte();
            if(success == 1)
            {
                DoLogin = false;
                DoPingPacket = false;

                // Login ID sent to the world server upon connect
                LoginID = reader.ReadDword();

                WORD len = reader.ReadWord();
                char* WorldIP = new char[len + 2];
                reader.ReadString(len, WorldIP);
                WORD WorldPort = reader.ReadWord();

                // Connect to the world server
                sSocket->disconnectFromHost();
                sSocket->connectToHost(WorldIP, WorldPort);

                // Free memory
                delete [] WorldIP;
            }
            else
            {
                BYTE error = reader.ReadByte();
                if(error == 5)
                {
                    // Login delay
                    LoginPacketTime = reader.ReadInt();

                    // Can start the auto login process
                    DoLogin = true;
                }
                else
                {
                    // Reset boxes
                    mainw->ui.Username->setText("");
                    mainw->ui.Password->setText("");
                    mainw->ui.Login->setEnabled(true);
                    DoLogin = false;

                    if(error == 1)
                    {
                        Log("Invalid username/password");
                    }
                    else
                    {
                        Log("Unknown error at the login server %.2X\n", error);
                    }
                }
            }
        }break;
        case 0xA103:
        {
            if(reader.ReadByte() == 1)
            {
                Log("Login success. Select your character");
            }
            else
            {
                BYTE error = reader.ReadByte();
                if(error == 5)
                {
                    Log("Unknown error while connecting to the world server %.2X", error);
                }
            }
        }break;
        default:
        {
        }
    }
}

void PacketProcessorz::OnTimer()
{
    if(DoPingPacket)
    {
        // Has 5 seconds passed?
        if((GetTickCount() - PingPacketTime) >= 5)
        {
            builder.SetOpcode(0x2002);
            InjectPacket(builder.GetPacket(), false);
        }
    }

    if(DoLogin)
    {
        LoginPacketTime -= 1;
        if(LoginPacketTime <= 0)
        {
            builder.SetOpcode(0x6102);
            builder.AppendByte(18);
            builder.AppendWord(strlen(username));
            builder.AppendString(username);
            builder.AppendWord(strlen(password));
            builder.AppendString(password);
            builder.AppendByte(0xFF);
            builder.AppendWord(serverid);
            InjectPacket(builder.GetPacket(), true);
        }
    }
}

void PacketProcessorz::InjectPacket(tPacket* packet, bool encrypted)
{
    // Ping packet delay
    PingPacketTime = GetTickCount();

    // Size of the packet
    DWORD realSize = packet->size;

    // Temp buffer
    BYTE tmpBuffer[8192] = {0};

    // Copy over the packet to send
    memcpy(tmpBuffer, packet, realSize + 6);

    // Make a packet pointer
    tPacket* tmpPacket = (tPacket *)tmpBuffer;

    // Fix the size if it's encrypted
    if(encrypted)
        tmpPacket->size |= 0x8000;

    // Generate count byte
    tmpPacket->securityCount = HandShakeApi.GenerateCountByte();

    // Generate CRC byte
    tmpPacket->securityCRC = 0;
    tmpPacket->securityCRC = HandShakeApi.GenerateCheckByte((char*)tmpBuffer, realSize + 6, HandShakeApi.GetCRCSeed());

    // Check to see if we need to encode the packet
    if(encrypted)
    {
        // Get blowfish data size
        DWORD bfSize = HandShakeApi.blowfish.GetOutputLength(realSize + 4);

        // Encode the packet
        HandShakeApi.blowfish.Encode((BYTE*)tmpBuffer + 2, (BYTE*)tmpBuffer + 2, realSize + 4);

        sSocket->write((char*)tmpBuffer, bfSize + 2);
    }
    else
    {
        // Forward the packet
        sSocket->write((char*)tmpBuffer, realSize + 6);
    }
}
